const axios = require('axios');
const config = require('../config/config');

class GeminiService {
  constructor() {
    if (!config.gemini.apiKey) {
      throw new Error('GEMINI_API_KEY is not configured');
    }
    this.apiKey = config.gemini.apiKey;
    this.baseUrl = config.gemini.baseUrl;
    this.model = config.gemini.model;
    this.generationConfig = config.gemini.generationConfig;
    this.retryDelay = 60000;
    this.maxRetries = 3;
  }

  async makeRequest(endpoint, data) {
    const url = `${this.baseUrl}/v1beta/models/${this.model}:${endpoint}?key=${this.apiKey}`;

    try {
      const response = await axios.post(url, data, {
        headers: {
          'Content-Type': 'application/json',
        },
        timeout: 120000, // 2 minutes timeout
      });
      return response.data;
    } catch (error) {
      if (error.response) {
        throw {
          status: error.response.status,
          statusText: error.response.statusText,
          message: error.response.data?.error?.message || error.message,
          errorDetails: error.response.data?.error?.details,
        };
      }
      throw error;
    }
  }

  async generateImage(prompt, options = {}) {
    return this.executeWithRetry(async () => {
      const requestData = {
        contents: [{
          role: 'user',
          parts: [{
            text: prompt
          }]
        }],
        generationConfig: this.generationConfig,
        ...options
      };

      const response = await this.makeRequest('generateContent', requestData);

      if (!response || !response.candidates || response.candidates.length === 0) {
        throw new Error('No image generated');
      }

      const candidate = response.candidates[0];

      if (!candidate.content || !candidate.content.parts) {
        throw new Error('Invalid response format');
      }

      const imageParts = candidate.content.parts.filter(part => part.inlineData && part.inlineData.mimeType);

      if (imageParts.length === 0) {
        throw new Error('No image data in response');
      }

      return {
        images: imageParts.map(part => ({
          mimeType: part.inlineData.mimeType,
          data: part.inlineData.data
        })),
        text: candidate.content.parts
          .filter(part => part.text)
          .map(part => part.text)
          .join('\n')
      };
    }, 'generate image');
  }

  async editImage(imageData, mimeType, prompt, options = {}) {
    return this.executeWithRetry(async () => {
      const requestData = {
        contents: [{
          role: 'user',
          parts: [
            {
              inlineData: {
                mimeType: mimeType,
                data: imageData
              }
            },
            {
              text: prompt
            }
          ]
        }],
        generationConfig: this.generationConfig,
        ...options
      };

      const response = await this.makeRequest('generateContent', requestData);

      if (!response || !response.candidates || response.candidates.length === 0) {
        throw new Error('No edited image generated');
      }

      const candidate = response.candidates[0];

      if (!candidate.content || !candidate.content.parts) {
        throw new Error('Invalid response format');
      }

      const imageParts = candidate.content.parts.filter(part => part.inlineData && part.inlineData.mimeType);

      if (imageParts.length === 0) {
        throw new Error('No image data in response');
      }

      return {
        images: imageParts.map(part => ({
          mimeType: part.inlineData.mimeType,
          data: part.inlineData.data
        })),
        text: candidate.content.parts
          .filter(part => part.text)
          .map(part => part.text)
          .join('\n')
      };
    }, 'edit image');
  }

  async composeImages(images, prompt, options = {}) {
    return this.executeWithRetry(async () => {
      const parts = [];

      images.forEach(image => {
        parts.push({
          inlineData: {
            mimeType: image.mimeType,
            data: image.data
          }
        });
      });

      parts.push({
        text: prompt
      });

      const requestData = {
        contents: [{
          role: 'user',
          parts: parts
        }],
        generationConfig: this.generationConfig,
        ...options
      };

      const response = await this.makeRequest('generateContent', requestData);

      if (!response || !response.candidates || response.candidates.length === 0) {
        throw new Error('No composed image generated');
      }

      const candidate = response.candidates[0];

      if (!candidate.content || !candidate.content.parts) {
        throw new Error('Invalid response format');
      }

      const imageParts = candidate.content.parts.filter(part => part.inlineData && part.inlineData.mimeType);

      if (imageParts.length === 0) {
        throw new Error('No image data in response');
      }

      return {
        images: imageParts.map(part => ({
          mimeType: part.inlineData.mimeType,
          data: part.inlineData.data
        })),
        text: candidate.content.parts
          .filter(part => part.text)
          .map(part => part.text)
          .join('\n')
      };
    }, 'compose images');
  }

  async chat(messages, options = {}) {
    return this.executeWithRetry(async () => {
      const requestData = {
        contents: messages.map(msg => ({
          role: msg.role,
          parts: msg.parts || [{ text: msg.content }]
        })),
        generationConfig: this.generationConfig,
        ...options
      };

      const response = await this.makeRequest('generateContent', requestData);

      if (!response || !response.candidates || response.candidates.length === 0) {
        throw new Error('No response generated');
      }

      const candidate = response.candidates[0];

      return {
        images: candidate.content.parts
          .filter(part => part.inlineData && part.inlineData.mimeType)
          .map(part => ({
            mimeType: part.inlineData.mimeType,
            data: part.inlineData.data
          })),
        text: candidate.content.parts
          .filter(part => part.text)
          .map(part => part.text)
          .join('\n')
      };
    }, 'chat');
  }

  async executeWithRetry(operation, operationName) {
    let lastError;
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;
        console.error(`Gemini API error (attempt ${attempt}/${this.maxRetries}):`, error);

        if (error.status === 429) {
          const retryAfter = this.parseRetryAfter(error);
          const delay = retryAfter || this.retryDelay * attempt;

          if (attempt < this.maxRetries) {
            console.log(`Rate limit exceeded. Retrying after ${delay}ms...`);
            await this.sleep(delay);
            continue;
          }
        }

        if (error.status >= 500 && attempt < this.maxRetries) {
          const delay = this.retryDelay * attempt;
          console.log(`Server error. Retrying after ${delay}ms...`);
          await this.sleep(delay);
          continue;
        }

        throw {
          statusCode: error.status || 500,
          message: error.message || `Failed to ${operationName}`,
          status: error.status,
          retryAfter: error.status === 429 ? this.parseRetryAfter(error) : null
        };
      }
    }

    throw {
      statusCode: lastError.status || 500,
      message: lastError.message || `Failed to ${operationName} after ${this.maxRetries} attempts`,
      status: lastError.status
    };
  }

  parseRetryAfter(error) {
    if (error.errorDetails) {
      const retryInfo = error.errorDetails.find(detail =>
        detail['@type'] === 'type.googleapis.com/google.rpc.RetryInfo'
      );
      if (retryInfo && retryInfo.retryDelay) {
        const match = retryInfo.retryDelay.match(/(\d+)s/);
        if (match) {
          return parseInt(match[1]) * 1000;
        }
      }
    }
    return null;
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

module.exports = new GeminiService();